/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 adv_object_model.cpp

	$Header: /heroes4/adv_object_model.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "adv_object_model.h"

#include "adv_object_image_visitor.h"
#include "overlay_adv_object_image.h"
#include "pixel_24.h"
#include "sized_int_types.h"
#include "streambuf_operators.h"
#include "underlay_adv_object_image.h"

/////////////////////////////////////////////////////////////////////////////
// Unnamed namespace

namespace
{

	/////////////////////////////////////////////////////////////////////////////
	int const k_current_format_version = 6;
	int const k_current_footprint_format_version = 1;

	/////////////////////////////////////////////////////////////////////////////
	// t_adv_object_image_type enum

	enum t_adv_object_image_type
	{
		k_overlay_adv_object_image,
		k_underlay_adv_object_image,

		k_adv_object_image_type_count,
	};

	/////////////////////////////////////////////////////////////////////////////
	// t_adv_object_image_writer class

	class t_adv_object_image_writer : private t_adv_object_image_visitor_24
	{
	public:
		// Operators
		bool operator()( std::streambuf & stream, t_adv_object_image_24 const & image );

	private:
		// Data members
		std::streambuf *	m_stream_ptr;
		bool				m_result;

		// Member functions
		virtual void visit( t_underlay_adv_object_image_24 const & image );
		virtual void visit( t_overlay_adv_object_image_24 const & image );
	};

	bool t_adv_object_image_writer::operator()( std::streambuf & stream, t_adv_object_image_24 const & image )
	{
		m_result = false;
		m_stream_ptr = &stream;
		image.accept( *this );
		return m_result;
	}

	void t_adv_object_image_writer::visit( t_underlay_adv_object_image_24 const & image )
	{
		try
		{
			put< t_int8 >( *m_stream_ptr, k_underlay_adv_object_image );
			m_result = image.write( *m_stream_ptr );
		}
		catch ( t_streambuf_write_failure const & )
		{
		}
	}

	void t_adv_object_image_writer::visit( t_overlay_adv_object_image_24 const & image )
	{
		try
		{
			put< t_int8 >( *m_stream_ptr, k_overlay_adv_object_image );
			m_result = image.write( *m_stream_ptr );
		}
		catch ( t_streambuf_write_failure const & )
		{
		}
	}

	/////////////////////////////////////////////////////////////////////////////
	std::auto_ptr< t_adv_object_image_24 > reconstruct_adv_object_image( std::streambuf & stream, int version )
	{
		t_adv_object_image_type image_type = static_cast< t_adv_object_image_type >( get< t_int8 >( stream ) );
		if ( image_type < 0 || image_type >= k_adv_object_image_type_count )
			return std::auto_ptr< t_adv_object_image_24 >();

		std::auto_ptr< t_adv_object_image_24 > result;
		switch ( image_type )
		{
		case k_underlay_adv_object_image:
			{
				std::auto_ptr< t_underlay_adv_object_image_24 > underlay_ptr( new t_underlay_adv_object_image_24 );
				bool read_result;
				if ( version >= 1 )
					read_result = underlay_ptr->read( stream );
				else
					read_result = underlay_ptr->read_version( stream, 0 );
				if ( read_result )
					result = std::auto_ptr< t_adv_object_image_24 >( underlay_ptr.release() );
			}
			break;

		case k_overlay_adv_object_image:
			{
				std::auto_ptr< t_overlay_adv_object_image_24 > overlay_ptr( new t_overlay_adv_object_image_24 );
				bool read_result;
				if ( version >= 1 )
					read_result = overlay_ptr->read( stream );
				else
					read_result = overlay_ptr->read_version( stream, 0 );
				if ( read_result )
					result = std::auto_ptr< t_adv_object_image_24 >( overlay_ptr.release() );
			}
			break;
		}

		return result;
	}

} // Unnamed namespace

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model_base_base members

/////////////////////////////////////////////////////////////////////////////
inline bool t_adv_object_model_base_base::are_any_cells_impassable_impl() const
{
	return		std::find(
					m_cell_impassable_vector.begin(),
					m_cell_impassable_vector.end(),
					true )
			!=	m_cell_impassable_vector.end();
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::are_any_cells_flat() const
{
	switch ( m_flatten_type )
	{
	case k_adv_object_flatten_none:
		return false;

	case k_adv_object_flatten_impassable:
		return are_any_cells_impassable_impl();

	case k_adv_object_flatten_all:
		return true;
	}

	return false;
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::are_any_cells_impassable() const
{
	return are_any_cells_impassable_impl();
}

/////////////////////////////////////////////////////////////////////////////
t_map_point_2d t_adv_object_model_base_base::get_size() const
{
	return m_size;
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_cell_flat( t_map_point_2d const & point ) const
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );

	switch ( m_flatten_type )
	{
	case k_adv_object_flatten_none:
		return false;

	case k_adv_object_flatten_impassable:
		return is_cell_impassable_impl( point );

	case k_adv_object_flatten_all:
		return true;
	}

	return false;
}
/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_cell_impassable( t_map_point_2d const & point ) const
{
	return is_cell_impassable_impl( point );
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_left_edge_trigger( t_map_point_2d const & point ) const
{
	return is_left_edge_trigger_impl( point );
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_right_edge_trigger( t_map_point_2d const & point ) const
{
	return is_right_edge_trigger_impl( point );
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_trigger_cell( t_map_point_2d const & point ) const
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );

	if (	is_left_edge_trigger_impl( point ) 
		&&	( point.column == 0 || is_cell_impassable_impl( point ) ) )
		return true;

	if (	is_right_edge_trigger_impl( point )
		&&	( point.row == 0 || is_cell_impassable_impl( point ) ) )
		return true;

	if (	is_left_edge_trigger_impl( t_map_point_2d( point.row, point.column + 1 ) )
		&&	( point.column + 1 == m_size.column || is_cell_impassable_impl( point ) ) )
		return true;

	if (	is_right_edge_trigger_impl( t_map_point_2d( point.row + 1, point.column ) )
		&&	( point.row + 1 == m_size.row || is_cell_impassable_impl( point ) ) )
		return true;

	return false;
}

static bool read_bool_array( std::streambuf& stream, std::vector<bool>& array )
{
	std::vector< t_uint8 > buffer;

	// Read in the left edge trigger flags
	buffer.resize( ( array.size() + 7 ) >> 3 );
	if ( read( stream, &buffer[ 0 ], buffer.size() ) != buffer.size() )
		return false;

	int edge_num;

	for ( edge_num = 0; edge_num < array.size(); ++edge_num )
		array[ edge_num ] = ( buffer[ edge_num >> 3 ] & ( 1 << ( edge_num & 7 ) ) ) != 0;
	return true;
}


/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::read( std::streambuf & stream, int format_version )
{
	int new_frames_per_second;
	t_qualified_adv_object_type new_qualified_type;
	t_flatten_type new_flatten_type;
	t_map_point_2d new_size;
	bool new_has_flag;
	t_screen_point new_flag_offset;
	int					left_edge_size;
	int					right_edge_size;
	std::vector< bool > new_cell_impassable_vector;
	std::vector< bool > new_left_edge_trigger_vector;
	std::vector< bool > new_right_edge_trigger_vector;

	if (format_version > k_current_format_version)
		return false;

	try
	{
		new_frames_per_second = get< t_uint8 >( stream );

		// Read the footprint info
		int footprint_format_version;
		if ( format_version >= 3 )
		{
			footprint_format_version = get< t_uint16 >( stream );
			if (	footprint_format_version < 0
				||	footprint_format_version > k_current_footprint_format_version )
				return false;
		}
		else
			footprint_format_version = 0;

		new_size.row = get< t_uint16 >( stream );
		new_size.column = get< t_uint16 >( stream );

		left_edge_size = new_size.row * (new_size.column + 1);
		right_edge_size = new_size.column * (new_size.row + 1 );
		new_cell_impassable_vector.resize( new_size.row * new_size.column, false );
		new_left_edge_trigger_vector.resize( left_edge_size, false );
		new_right_edge_trigger_vector.resize( right_edge_size, false );
		m_left_edge_blocked.resize( left_edge_size << 1, false );
		m_right_edge_blocked.resize( right_edge_size << 1, false );
		m_vertex_height.resize( (new_size.row + 1) * (new_size.column + 1), 0);

		// Read in the cell impassability flags
		if (!read_bool_array( stream, new_cell_impassable_vector ))
			return false;

		if ( footprint_format_version >= 1 )
		{
			// Read in the left edge trigger flags
			if (!read_bool_array( stream, new_left_edge_trigger_vector ))
				return false;
			// Read in the right edge trigger flags
			if (!read_bool_array( stream, new_right_edge_trigger_vector ))
				return false;
		}
		else
		{
			// Read and discard the old trigger flags
			std::vector<bool> buffer( new_cell_impassable_vector.size(), false );

			if (!read_bool_array( stream, buffer ))
				return false;
		}

		// read the left & right edge blocked flags
		if (format_version >= 6)
		{
			if (!read_bool_array( stream, m_left_edge_blocked ))
				return false;
			if (!read_bool_array( stream, m_right_edge_blocked ))
				return false;
			::read( stream, &m_vertex_height[0], m_vertex_height.size() );
		}

		// Read the adventure object type
		if ( format_version >= 2 )
			::read( stream, new_qualified_type );

		// Read the flatten type
		if ( format_version >= 4 )
		{
			new_flatten_type = t_flatten_type( get< t_uint8 >( stream ) );
			if ( new_flatten_type < 0 || new_flatten_type >= k_adv_object_flatten_type_count )
				return false;
		}
		else
		{
			if ( new_size.row <= 1 && new_size.column <= 1 )
				new_flatten_type = k_adv_object_flatten_none;
			else
				new_flatten_type = k_adv_object_flatten_impassable;
		}

		// Read the flag info
		if ( format_version >= 5 )
		{
			new_has_flag = get< t_uint8 >( stream ) != 0;
			if ( new_has_flag )
				stream >> new_flag_offset;
		}
		else
			new_has_flag = false;
	}
	catch ( t_streambuf_read_failure const & )
	{
		return false;
	}

	m_frames_per_second = new_frames_per_second;
	m_qualified_type = new_qualified_type;
	m_flatten_type = new_flatten_type;
	m_size = new_size;
	m_has_flag = new_has_flag;
	m_flag_offset = new_flag_offset;
	new_cell_impassable_vector.swap( m_cell_impassable_vector );
	new_left_edge_trigger_vector.swap( m_left_edge_trigger_vector );
	new_right_edge_trigger_vector.swap( m_right_edge_trigger_vector );
	return true;
}

/////////////////////////////////////////////////////////////////////////////
void t_adv_object_model_base_base::resize( t_map_point_2d const & new_size )
{
	assert( new_size.row > 0 && new_size.column > 0 );

	std::vector< bool > new_cell_impassable_vector( new_size.row * new_size.column, false );
	int					left_edge_size = new_size.row * (new_size.column + 1 );
	int					right_edge_size = new_size.column * (new_size.row + 1 );
	std::vector< bool > new_left_edge_trigger_vector( left_edge_size, false );
	std::vector< bool > new_right_edge_trigger_vector( right_edge_size, false );

	m_size = new_size;
	new_cell_impassable_vector.swap( m_cell_impassable_vector );
	new_left_edge_trigger_vector.swap( m_left_edge_trigger_vector );
	new_right_edge_trigger_vector.swap( m_right_edge_trigger_vector );
	m_left_edge_blocked.resize( left_edge_size << 1, false );
	m_right_edge_blocked.resize( right_edge_size << 1, false );
	m_vertex_height.resize( (new_size.row + 1) * (new_size.column + 1), 0 );
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::write( std::streambuf & stream ) const
{
	try
	{
		put< t_uint8 >( stream, m_frames_per_second );

		if( !write_footprint( stream ) || !::write( stream, m_qualified_type ) )
			return false;

		put< t_uint8 >( stream, m_flatten_type );

		put< t_uint8 >( stream, m_has_flag );
		if ( m_has_flag )
			stream << m_flag_offset;
	}
	catch ( t_streambuf_write_failure const & )
	{
		return false;
	}

	return true;
}

static bool write_bool_array( std::streambuf& stream, std::vector<bool> const& array )
{
	std::vector< t_uint8 > buffer( ( array.size() + 7 ) >> 3, 0 );

	int cell_num;
	for ( cell_num = 0; cell_num < array.size(); ++cell_num )
	{
		if ( array[ cell_num ] )
			buffer[ cell_num >> 3 ] |= 1 << ( cell_num & 7 );
	}
	if ( write( stream, &buffer[ 0 ], buffer.size() ) != buffer.size() )
		return false;
	return true;
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::write_footprint( std::streambuf & stream ) const
{
	try
	{
		// Write the current format version
		put< t_uint16 >( stream, k_current_footprint_format_version );

		// Write the size
		put< t_uint16 >( stream, m_size.row );
		put< t_uint16 >( stream, m_size.column );

		size_t const bits = std::numeric_limits< t_uint8 >::digits;

		// Write the impassability flags
		if (!write_bool_array( stream, m_cell_impassable_vector ))
			return false;

		// Write the left edge trigger flags
		if (!write_bool_array( stream, m_left_edge_trigger_vector ))
			return false;

		// Write the right edge trigger flags
		if (!write_bool_array( stream, m_right_edge_trigger_vector ))
			return false;

		if (!write_bool_array( stream, m_left_edge_blocked ))
			return false;
		if (!write_bool_array( stream, m_right_edge_blocked ))
			return false;
		::write( stream, &m_vertex_height[0], m_vertex_height.size() );
	}
	catch ( t_streambuf_write_failure const & )
	{
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// get index to vector for a blocked edge function
/////////////////////////////////////////////////////////////////////////////
inline
int t_adv_object_model_base_base::get_blocked_left_edge_index( t_map_point_2d const& point, 
														        bool under_bridge ) const
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column <= m_size.column );
	return ((( point.column * m_size.row ) + point.row) << 1) + under_bridge;
}

inline
int t_adv_object_model_base_base::get_blocked_right_edge_index( t_map_point_2d const& point, 
														        bool under_bridge ) const
{
	assert( point.row >= 0 && point.row <= m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );
	return ((( point.row * m_size.column ) + point.column) << 1) + under_bridge;
}

/////////////////////////////////////////////////////////////////////////////
// determine if edge is impassable
/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_base_base::is_left_edge_blocked( t_map_point_2d const& point,
													     bool				   under_bridge ) const
{
	return m_left_edge_blocked[ get_blocked_left_edge_index( point, under_bridge ) ];
}

bool t_adv_object_model_base_base::is_right_edge_blocked( t_map_point_2d const& point,
													     bool				   under_bridge ) const
{
	return m_right_edge_blocked[ get_blocked_right_edge_index( point, under_bridge ) ];
}

void t_adv_object_model_base_base::set_left_edge_blocked( t_map_point_2d const& point,
												          bool under_bridge, bool   blocked )
{
	m_left_edge_blocked[ get_blocked_left_edge_index( point, under_bridge )] = blocked;
}

void t_adv_object_model_base_base::set_right_edge_blocked( t_map_point_2d const& point,
														   bool under_bridge, bool blocked )
{
	m_right_edge_blocked[ get_blocked_right_edge_index( point, under_bridge )] = blocked;
}


/////////////////////////////////////////////////////////////////////////////
// get and set height offsets from ground at points
/////////////////////////////////////////////////////////////////////////////
int t_adv_object_model_base_base::get_vertex_height( t_map_point_2d const& point ) const
{
	return m_vertex_height[ point.row * (m_size.column + 1) + point.column ];
}

void t_adv_object_model_base_base::set_vertex_height( t_map_point_2d const& point, int value )
{
	m_vertex_height[ point.row * (m_size.column + 1) + point.column ] = value;
}

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model_24 members

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_24::read( std::streambuf & stream )
{
	int format_version;
	try
	{
		format_version = get< t_uint16 >( stream );
	}
	catch ( t_streambuf_read_failure const & )
	{
		return false;
	}

	if ( !t_adv_object_model_base_base::read( stream, format_version ) )
		return false;

	std::auto_ptr< t_adv_object_image_24 > image_ptr = reconstruct_adv_object_image( stream, format_version );
	if ( image_ptr.get() == 0 )
		return false;
	set_image( image_ptr );

	return true;
}

/////////////////////////////////////////////////////////////////////////////
bool t_adv_object_model_24::write( std::streambuf & stream ) const
{
	try
	{
		put< t_uint16 >( stream, k_current_format_version );
	}
	catch ( t_streambuf_write_failure const & )
	{
		return false;
	}

	if ( !t_adv_object_model_base_base::write( stream ) )
		return false;

	t_adv_object_image_writer write_adv_object_image;
	return write_adv_object_image( stream, get_image() );
}

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model members

/////////////////////////////////////////////////////////////////////////////
t_adv_object_model::t_adv_object_model( t_adv_object_model_24 const & other )
	:	t_adv_object_model_base< t_adv_object_image >( t_shared_ptr< t_adv_object_image >( other.get_image().clone_as_16() ), other )
{
}

/////////////////////////////////////////////////////////////////////////////
// t_resource_traits< t_adv_object_model_24 > class members

char const t_resource_traits< t_adv_object_model_24 >::prefix[] = "adv_object";

char const t_resource_traits< t_adv_object_model_24 >::extension[] = "psd";

